#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stddef.h>
#include <linux/input.h>

#include "hdf_base.h"
#include "hdf_log.h"

#define VIBRATOR_FILE_LEN 64

static int vibra_ff_id = -1;
static int vibra_ff_fd = -1;
static int vibra_event_id = 0;
static const char *INPUT_DEVICE = "/dev/input";
static const char *ff_vibrators[] = {
    "sc27xx:vibrator",
};

static bool device_exists(const char *file) {
    int fd;

    fd = open(file, O_RDWR);
    if (fd < 0) {
        HDF_LOGE("%s: open %s failed, errno=%d(%s)",
                 __func__, file, errno, strerror(errno));
        return false;
    }

    close(fd);
    return true;
}

static bool is_vibra_ff(const char *name)
{
    unsigned int i = 0;

    for (i = 0; i < sizeof(ff_vibrators)/sizeof(ff_vibrators[0]); i++) {
        if (!strncmp(ff_vibrators[i], name, strlen(ff_vibrators[i])))
            return true;
    }

    return false;
}

static int vibra_ff_lookup()
{
    int found = -1;
    int fd = -1;
    DIR *dir = NULL;
    struct dirent *result = NULL;
    char file_name[300] = {0};
    char buf[64] = {0};

    dir = opendir(INPUT_DEVICE);
    if (!dir) {
        HDF_LOGE("%s: open %s failed, errno=%d(%s)",
                 __func__, INPUT_DEVICE, errno, strerror(errno));
        return -1;
    }

    while ((result = readdir(dir)) != NULL) {
        if (strncmp(result->d_name, "event", strlen("event")))
            continue;
        memset(file_name, 0, sizeof(file_name));
        snprintf(file_name, sizeof(file_name) - 1, "%s/%s", INPUT_DEVICE, result->d_name);
        fd = open(file_name, O_RDWR);
        if (fd < 0) {
            HDF_LOGE("%s: open %s failed, errno=%d(%s)",
                     __func__, file_name, errno, strerror(errno));
            continue;
        }
        memset(buf, 0, sizeof(buf));
        ioctl(fd, EVIOCGNAME(sizeof(buf) - 1), buf);
        close(fd);
        fd = -1;
        if (is_vibra_ff(buf)) {
            sscanf(result->d_name, "event%d", &found);
            break;
        } else {
            HDF_LOGI("%s: %s cannot open using force feedback",
                     __func__, file_name);
        }
    }

    closedir(dir);
    dir = NULL;

    return found;
}

bool vibra_ff_exists()
{
    char file_str[VIBRATOR_FILE_LEN] = {0};

    vibra_event_id = vibra_ff_lookup();
    if (vibra_event_id < 0)
        return false;

    snprintf(file_str, sizeof(file_str), "%s/event%d", INPUT_DEVICE, vibra_event_id);

    return device_exists(file_str);
}

static int vibra_ff_upload(unsigned int timeout_ms)
{
    int ret = 0;
    struct ff_effect effect;

    if (vibra_ff_fd < 0)
        return -1;

    memset(&effect, 0, sizeof(effect));
    effect.type = FF_RUMBLE;
    effect.id = (vibra_ff_id >= 0) ? vibra_ff_id : -1;
    effect.replay.delay = 0;
    effect.replay.length = timeout_ms;
    effect.u.rumble.weak_magnitude = 1;

    ret = ioctl(vibra_ff_fd, EVIOCSFF, &effect);
    if (ret) {
        HDF_LOGE("%s: upload ff effect failed, errno=%d(%s)",
                 __func__, errno, strerror(errno));
        return ret;
    }

    if (vibra_ff_id < 0)
        vibra_ff_id = effect.id;

    return ret;
}

static int vibra_ff_erase()
{
    int ret = 0;

    if (vibra_ff_fd < 0)
        return -1;

    ret = ioctl(vibra_ff_fd, EVIOCRMFF, (void *)((unsigned long)vibra_ff_id));
    if (0 != ret)
        HDF_LOGE("%s: erase ff effect failed, errno=%d(%s)",
                 __func__, errno, strerror(errno));
    vibra_ff_id = -1;

    return 0;
}

static int vibra_ff_write(unsigned int count)
{
    int ret = 0;
    struct input_event event;

    if (vibra_ff_fd < 0)
        return -1;

    memset(&event, 0, sizeof(event));
    event.type = EV_FF;
    event.code = vibra_ff_id;
    event.value = count;

    ret = write(vibra_ff_fd, (const void *)&event, sizeof(event));
    if (ret != sizeof(event))
        ret = -EAGAIN;
    else
        ret = 0;

    return ret;
}

int vibra_ff_on(unsigned int timeout_ms)
{
    char file_str[VIBRATOR_FILE_LEN] = {0};

    snprintf(file_str, sizeof(file_str), "%s/event%d", INPUT_DEVICE, vibra_event_id);

    if (vibra_ff_fd < 0) {
        vibra_ff_fd = open(file_str, O_RDWR);
        if (vibra_ff_fd < 0)
            return -errno;
    }

    vibra_ff_upload(timeout_ms);
    vibra_ff_write(1);

    return 0;
}

int vibra_ff_off()
{
    if (vibra_ff_fd < 0)
        return 0;

    vibra_ff_write(0);
    vibra_ff_erase();

    close(vibra_ff_fd);
    vibra_ff_fd = -1;

    return 0;
}
